<?php

/**
 * Класс преобразует RQL-запросы в язык SQL
 * @author chedim
 * @category DAL
 * @package XDAL
 * @version 1
 *
 */

if (!function_exists('__autoload')) {
    include dirname(__FILE__).'/cercea/XMLConvertable.php';
    include dirname(__FILE__).'/cercea/JSONConvertable.php';
    include dirname(__FILE__).'/cercea/Set.php';
    include dirname(__FILE__).'/csda/RecordType.php';
    include dirname(__FILE__).'/csda/Record.php';
    include dirname(__FILE__).'/csda/RecordSet.php';
    include dirname(__FILE__).'/csda/MixedRecord.php';
    include dirname(__FILE__).'/RQLAutomato.php';
    include dirname(__FILE__).'/RQLAutomatoState.php';
    include dirname(__FILE__).'/RQLDescription.php';
    include dirname(__FILE__).'/RQLNode.php';
    include dirname(__FILE__).'/RQLFork.php';
    include dirname(__FILE__).'/RQLPath.php';
}

class Rql {

    protected $source;
    protected $sql;
    protected $checksum;
    protected static $cache = false;
    protected static $stype = 'cercea';
    protected static $sid = 1;
    protected static $slock = false;

    const ACL_SELECT = 1;
    const ACL_UPDATE = 2;
    const ACL_DELETE = 4;
    const ACL_CHILD_APPEND = 16;
    const ACL_CHILD_OPERATION = 32;
    const ACL_RELATION_OPERATION = 64;

    /**
     *
     * @var mysqli
     */
    protected static $connection;
    static function destroy_nodes($type,  $ids) {
        $ids = array_unique($ids);
        $query = "DELETE FROM {$type} WHERE `id` IN (".implode(',',$ids).")";
        self::query($query);
        $query = "DELETE FROM `relations` WHERE child = '{$type}' and `child_id` IN (".implode(',',$ids).")";
        self::query($query);
    }

    static protected function query($q) {
        if (!self::$connection->query($q)) {
            throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$q<br/><br/>\r\n\r\n".self::$connection->error);
        }
    }


    static function exec_delete($query, $order = null, $limit = null) {
        if (!$query) return;
	if ($order !== null) {
	    $query .= ' ORDER BY '.$order;
	}
	if ($limit !== null) {
	    $query .= ' LIMIt '.$limit;
	}
        $delete = array();
        if ($res = self::$connection->query($query)) {
            while($row = $res->fetch_array(MYSQLI_NUM)) {
                for($i=0;$i<count($row);$i=$i+2) {
                    $delete[$row[$i]][] = $row[$i+1];
                }
            }
            foreach($delete as $table => $ids) {
                self::drop_nodes($table, $ids);
            }
        } else {
            throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);

        }
    }
    static function exec_select($query, $types, $order = null, $limit = null) {
	if ($order !== null) {
	    $query .= ' ORDER BY '.$order;
	}
	if ($limit !== null) {
	    $query .= ' LIMIT '.$limit;
	}
        $res = self::$connection->query($query);
	if (!$res) return false;
        try {
	    $ret = new RecordSet($res, $types, $res->field_count);
	} catch (exception $e) {
	    if ($e->getCode() == 1011) return false;
	    else throw $e;
	}
        return $ret;
    }

    /**
     *
     * @param <type> $query
     * @param <type> $order
     * @param none $limit   ignored
     * @return <type>
     */
    static function exec_update($query, $order = null, $limit = null) {
        if (!$query) return;
	if ($order !== null) {
	    $query .= ' ORDER BY '.$order;
	}
        if (!self::$connection->query($query)) {
            throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
        }
    }


    function __construct($host = null, $user = null, $password = null, $database = null, $names = null) {

        // initializing connection parameters
        if (class_exists('X') && ($host === null || $names === null)) {
            // Config is a Xendri Framework module from a base package
            if ($host === null) {
                $host       = X::cfg('sql:host');
                $user       = X::cfg('sql:user');
                $password   = X::cfg('sql:password');
                $database   = X::cfg('sql:database');
            }

            if ($names === null) {
                $names      = X::cfg('sql:names');
            }
        }


        self::$connection = new mysqli($host, $user, $password, $database);
        if (mysqli_connect_errno()) {
            throw new Exception('Connection to database failed!');
        }
    }

    public function get_sql() {
        return $this->mk_sql($this->sql);
    }

    protected function translate($rql) {
        $automato = new RqlAutomato($rql);
        $automato->start();
        $res = $automato->result();
        return $arr['sql'];
    }

    public static function from($rql) {
        if (self::$connection == null) throw  new Exception('You MUST create at least one object of class RQL!');
        $chk7 = $this->checksum = md5($rql);
        if (!self::$cache) self::$cache = array();

        if (!key_exists($chk, self::$cache)) {
            $automato = new RqlAutomato($rql);
            $automato->start();
            $res = $automato->result();
            $this->sql = $res;
            self::$cache[$chk] = $this->sql;
        }

        return $arr['sql'];
    }

    /**
     *
     * @param string $rql RQL-query to database
     * @param string $order affected rows sorting options
     * @param string $limit affected rows limit options
     * @return RecordSet
     */
    public static function exec($rql, $order = null, $limit = null) {
        if (self::$connection == null) throw  new Exception('You MUST create at least one object of class RQL!');

	if (strpos(trim($rql), 'self') != 0) {
	    $rql = 'self / '.$rql;
	}

	$rql = str_replace('self', (self::$stype).'[id='.self::$sid.']', $rql);

	$automato = new RqlAutomato($rql);
        $automato->start();
        $res = $automato->result();
	var_dump($res);
	echo "<hr/>\r\n";
	echo "inserts: ".print_r($res->get_insert_queries(), 1)."\r\n";

        $insertion_result = self::exec_inserts($res->get_insert_queries());

	for($i=0;$i<count($insertion_result); $i++) {
            $res->update_description_by_real_name($insertion_result[$i]['name'], 'id', '=', $insertion_result[$i]['id']);
//            $res->drop_insertion_by_real_name($insertion_result[$i]['name']);
        }

	echo "update: ".$res->get_update_query()."\r\n";
        self::exec_update($res->get_update_query(), $order, $limit);

	echo "select: ".$res->get_select_query()."\r\n";
        $sel = self::exec_select($res->get_select_query(), $res->get_selected_real_names(), $order, $limit);
        self::exec_delete($res->get_delete_query(), $order, $limit);

        return $sel;
    }

    protected static function exec_inserts($inserts) {
        $holder = '##**RQL_NEW_NODE_ID_PLACEHOLDER**##';
        $return = array();
        for($i=0;$i<count($inserts);$i++) {
            $insert = $inserts[$i];
            $query = $insert['check'];
            $names = $insert['insert']['parent_type'];
            if (!is_array($names)) {
                $names = array($names);
            } else {
                $names = array_unique($names);
            }
            $ids = array();
            $values = array();
            if ($res = self::$connection->query($query)) {
                while(true == ($row = $res->fetch_array())) {
                    $ids[$row[0]][] = $row[1];
                    $values[] = "( 255, '{$row[0]}', '{$row[1]}', '{$insert['insert']}', '{$holder}')";
                }
            } else {
                throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
            }
            if (count($values) == 0) throw new Exception('There was no parents available for new node `'.$insert['insert'].'` (Yoda says: privileges insufficient maybe?)!');
            $query = "INSERT INTO `{$insert['insert']}` (`id`) VALUES ('')";
            if($res = self::$connection->query($query)) {
                $relations = "INSERT INTO `relations` (`acl`, `parent`, `parent_id`, `child`, `child_id`) VALUES ".implode(',', $values);
                $relations = str_replace($holder, self::$connection->insert_id, $relations);
                $return[] = array(
                    'name' => $insert['insert'],
                    'id' => self::$connection->insert_id
                );
                if (!self::$connection->query($relations)) {
                    throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
                }
            } else {
                throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
            }
        }

        return $return;

    }

    protected function drop_nodes($type, $ids) {
        $ids = array_unique($ids);
        $query = "UPDATE `relations` SET `parent` = '---deleted---', `parent_id` = -1 WHERE (`child` = '{$type}' AND `child_id` IN (".implode(',',$ids).")) OR (`parent` = '{$type}' AND `parent_id` IN (".implode(',',$ids)."))";
        if (!self::$connection->query($query)) {
            throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
        }
        self::clear_database();
    }

    /*
     * clears deleted nodes from database
     */
    protected static function clear_database() {
        $query = 'select distinct deleted.child, deleted.child_id '.
            'from relations as deleted '.
            'left join relations as remains ON '.
            'remains.child_id = deleted.child_id AND '.
            'remains.child = deleted.child AND '.
            'remains.parent_id != -1 '.
            'where deleted.parent_id = -1 and coalesce(remains.parent_id, 0) = 0 '.
            'group by deleted.child, deleted.child_id';
        $result = self::$connection->query($query);
        if (!$result) {
            throw new Exception(self::$connection->error."<br/><br/>\r\n\r\n$query<br/><br/>\r\n\r\n".self::$connection->error);
        }

        $drops = array();
        while($row = $result->fetch_array(MYSQLI_NUM)) {
            $drops[$row[0]][] = $row[1];
        }

        var_dump($drops);

        foreach ($drops as $type=>$ids) {
            self::destroy_nodes($type, $ids);
        }

        if(count($drops) > 0) self::clear_database();
    }

    static function get_connection() {
        return self::$connection;
    }

    static function set_self(Record $rec, $lock = true) {
	if (self::$slock) return false;
	self::$slock = $lock;
	self::$stype = $rec->get_type();
	self::$sid = $rec->id();
	return true;
    }

}
?>